package com.biz.primus.ms.coupon.service;

import com.biz.primus.base.exception.BizSilentException;
import com.biz.primus.base.exception.utils.AssertUtils;
import com.biz.primus.common.utils.CopyUtils;
import com.biz.primus.common.utils.ValueUtils;
import com.biz.primus.common.vo.SpringPageVO;
import com.biz.primus.model.coupon.enums.CouponState;
import com.biz.primus.model.coupon.exceptions.CouponExceptionType;
import com.biz.primus.model.coupon.exceptions.CouponTypeExceptionType;
import com.biz.primus.model.coupon.vo.couponType.AppendCouponTypeIssuedCountVo;
import com.biz.primus.model.coupon.vo.couponType.CouponTypeDetailVo;
import com.biz.primus.model.coupon.vo.couponType.CouponTypeVo;
import com.biz.primus.model.coupon.vo.couponType.UpdateCouponTypeInfoReqVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.coupon.config.CouponModuleConfig;
import com.biz.primus.ms.coupon.dao.po.CouponType;
import com.biz.primus.ms.coupon.dao.redis.CouponTypeRedisDao;
import com.biz.primus.ms.coupon.dao.repository.CouponTypeRepository;
import com.biz.primus.ms.coupon.dao.ro.CouponRo;
import com.biz.primus.ms.coupon.dao.ro.CouponTypeRo;
import com.biz.primus.ms.coupon.event.couponType.*;
import com.biz.primus.ms.coupon.trans.CouponType2CouponTypeDetailVo;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.biz.primus.model.coupon.enums.CouponGenerateStrategy.FETCH_BUILD;
import static com.biz.primus.model.coupon.enums.CouponGenerateStrategy.PRE_BUILD;

/**
 * CouponTypeServiceImpl
 *
 * @author david-liu
 */
@Service
@Slf4j
public class CouponTypeServiceImpl extends AbstractBaseService implements CouponTypeService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private CouponTypeRepository couponTypeRepository;

    @Autowired
    private CouponTypeRedisDao couponTypeRedisDao;

    /**
     * 创建优惠券类型
     *
     * @param couponTypeVo 优惠券类型Vo
     * @return true: 创建成功, false: 创建失败
     */
    @Override
    @Transactional
    public Boolean createCouponType(CouponTypeVo couponTypeVo) {
        CouponType couponType = new CouponType();
        CopyUtils.copyProperties(couponTypeVo, couponType);
        int preBuildCouponSizeLimit = CouponModuleConfig.getInstance().getPreBuildCouponSizeLimit();
        int couponTotalQuantity = couponType.getTotalQuantity();
        couponType.setId(idService.getNextId());
        couponType.setCouponGenerateStrategy(couponTotalQuantity >= preBuildCouponSizeLimit ? FETCH_BUILD : PRE_BUILD);
        if (CollectionUtils.isNotEmpty(couponTypeVo.getBrands())) {
            couponType.setBrands(Sets.newTreeSet(couponTypeVo.getBrands()));
        }
        if (CollectionUtils.isNotEmpty(couponTypeVo.getCategories())) {
            couponType.setCategories(Sets.newTreeSet(couponTypeVo.getCategories()));
        }
        if (CollectionUtils.isNotEmpty(couponTypeVo.getIncludeProducts())) {
            couponType.setIncludeProducts(Sets.newTreeSet(couponTypeVo.getIncludeProducts()));
        }
        if (CollectionUtils.isNotEmpty(couponTypeVo.getExcludeProducts())) {
            couponType.setExcludeProducts(Sets.newTreeSet(couponTypeVo.getExcludeProducts()));
        }
        if (CollectionUtils.isNotEmpty(couponTypeVo.getVendors())) {
            couponType.setVendors(Sets.newTreeSet(couponTypeVo.getVendors()));
        }
        try {
            couponType = couponTypeRepository.save(couponType);
            applicationEventPublisher.publishEvent(new CreateCouponTypeEvent(this, couponType));
        } catch (Exception e) {
            log.error("创建优惠券类型失败", e);
            throw new BizSilentException("创建优惠券类型失败", e);
        }
        return true;
    }

    @Override
    @SuppressWarnings("unchecked")
    public SpringPageVO<CouponTypeDetailVo> couponTypes(String couponTypeName, int page, int pageSize) {
        PageRequest pageRequest = new PageRequest(page, pageSize);
        Page<CouponType> couponTypePage;
        if (StringUtils.isNotBlank(couponTypeName)) {
            couponTypeName = String.format("%%%s%%", couponTypeName);
            couponTypePage = couponTypeRepository.findByCouponTypeNameLikeAndEnable(couponTypeName, true, pageRequest);
        } else {
            couponTypePage = couponTypeRepository.findByEnable(true, pageRequest);
        }

        CouponType2CouponTypeDetailVo transformer = new CouponType2CouponTypeDetailVo();
        return SpringPageVO
                .<CouponTypeDetailVo>builder()
                .content(Lists.transform(Optional.ofNullable(couponTypePage.getContent()).orElse(Lists.newArrayList()), transformer))
                .size(couponTypePage.getSize())
                .totalElements(couponTypePage.getTotalElements())
                .number(couponTypePage.getNumber())
                .build();
    }

    @Override
    @Transactional
    public Boolean updateCouponType(Long couponTypeId, CouponTypeVo couponTypeVo) {
        CouponType couponType = couponTypeRepository.findOne(couponTypeId);
        AssertUtils.notNull(couponType, CouponTypeExceptionType.COUPON_TYPE_ILLEGAL_STATE);
        CopyUtils.copyProperties(couponTypeVo, couponType);
        try {
            couponType = couponTypeRepository.save(couponType);
            applicationEventPublisher.publishEvent(new UpdateCouponTypeEvent(this, couponType));
        } catch (Exception e) {
            log.error("更改优惠券类型失败, 优惠券类型ID: {}", couponTypeId, e);
            throw new BizSilentException(String.format("更改优惠券类型失败, 优惠券类型ID: %s", couponTypeId), e);
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean deleteCouponType(Long couponTypeId, String deleteUser) {
        CouponType couponType = couponTypeRepository.findOne(couponTypeId);
        AssertUtils.notNull(couponType, CouponTypeExceptionType.COUPON_TYPE_ILLEGAL_STATE);
        couponType.setDeletedAt(LocalDateTime.now());
        couponType.setDeleteStatus(Boolean.TRUE);
        couponType.setLastUpdateUser(deleteUser);
        try {
            couponTypeRepository.save(couponType);
        } catch (Exception e) {
            log.error("删除优惠券类型失败, 优惠券类型ID: {}", couponTypeId, e);
            throw new BizSilentException(String.format("删除优惠券类型失败, 优惠券类型ID: %s", couponTypeId), e);
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean updateCouponTypeBaseInfo(Long couponTypeId, UpdateCouponTypeInfoReqVo reqVo) {
        CouponType couponType = couponTypeRepository.findOne(couponTypeId);
        AssertUtils.notNull(couponType, CouponTypeExceptionType.COUPON_TYPE_ILLEGAL_STATE);
        if (StringUtils.isNotBlank(reqVo.getLastUpdateUser())) {
            couponType.setLastUpdateUser(reqVo.getLastUpdateUser());
        }
        if (StringUtils.isNotBlank(reqVo.getCouponTypeName())) {
            couponType.setCouponTypeName(reqVo.getCouponTypeName());
        }
        if (StringUtils.isNotBlank(reqVo.getCouponTypeDesc())) {
            couponType.setCouponTypeDesc(reqVo.getCouponTypeDesc());
        }
        if (StringUtils.isNotBlank(reqVo.getIcon())) {
            couponType.setIcon(reqVo.getIcon());
        }
        if (StringUtils.isNotBlank(reqVo.getRemark())) {
            couponType.setRemark(reqVo.getRemark());
        }
        try {
            couponType = couponTypeRepository.save(couponType);
            applicationEventPublisher.publishEvent(new UpdateCouponTypeInfoEvent(this, couponType));
        } catch (Exception e) {
            log.error("删除优惠券类型失败, 优惠券类型ID: {}", couponTypeId, e);
            throw new BizSilentException(String.format("删除优惠券类型失败, 优惠券类型ID: %s", couponTypeId), e);
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean updateCouponTypeEnableStatus(List<Long> couponTypeIds, Boolean status) {
        List<CouponType> couponTypes = couponTypeRepository.findByIdAndDeleteStatus(couponTypeIds, Boolean.FALSE);
        if (CollectionUtils.isEmpty(couponTypes)) {
            return true;
        }
        couponTypes.forEach(couponType -> couponType.setEnable(status));
        try {
            couponTypes = couponTypeRepository.save(couponTypes);
            applicationEventPublisher.publishEvent(new UpdateCouponTypeStatusEvent(this, couponTypes, status));
        } catch (Exception e) {
            log.error("更新优惠券状态失败", e);
            throw new BizSilentException("更新优惠券状态失败", e);
        }
        return true;
    }

    @Override
    @Transactional
    public Boolean appendCouponTypeIssuedCount(AppendCouponTypeIssuedCountVo vo) {
        Long couponTypeId = vo.getCouponTypeId();
        CouponType couponType = couponTypeRepository.findOne(couponTypeId);
        AssertUtils.isTrue(couponTypeRedisDao.isExists(vo.getCouponTypeId()) && Objects.nonNull(couponType), CouponTypeExceptionType.COUPON_TYPE_ILLEGAL_STATE);
        couponTypeRedisDao.incrTotalQuantity(couponTypeId, vo.getAppendingIssuedCount());
        CouponTypeRo couponTypeRo = couponTypeRedisDao.findOne(couponTypeId);
        couponType.setTotalQuantity(couponTypeRo.getTotalQuantity());
        try {
            couponTypeRepository.save(couponType);
            applicationEventPublisher.publishEvent(new AppendCouponTypeIssuedCountEvent(this, couponTypeRo, vo.getAppendingIssuedCount()));
        } catch (Exception e) {
            log.error("更新优惠券发行数量失败", e);
            couponTypeRedisDao.incrTotalQuantity(couponTypeId, -vo.getAppendingIssuedCount());
            throw new BizSilentException("更新优惠券发行数量失败", e);
        }
        return true;
    }

    @Override
    public List<CouponRo> generateToBeFetchedCoupons(Long couponTypeId, Integer totalCount) {
        AssertUtils.isTrue(ValueUtils.getValue(totalCount) > 0, CouponExceptionType.COUPON_ISSUE_COUNT_ILLEGAL);
        List<CouponRo> couponRos = Lists.newArrayList();
        IntStream.range(0, totalCount).forEach(i -> {
            CouponRo couponRo = new CouponRo();
            couponRo.setId(idService.getNextId());
            couponRo.setCouponState(CouponState.CAN_RECEIVE);
            couponRo.setCouponTypeId(couponTypeId);
            couponRo.setUsed(false);
            couponRo.setCreateTimestamp(new Timestamp(System.currentTimeMillis()));
            couponRo.setUpdateTimestamp(new Timestamp(System.currentTimeMillis()));
            couponRos.add(couponRo);
        });
        return couponRos;
    }
}
